// scintillaWin.cpp
#include <string>
using namespace std;

#include <stdio.h>
#include <ctype.h>
#include "scintillaWin.h"
#include "scilexer.h"
#include "file_utils.h"


char *mKeywords =
"auto bool break case catch char class const const_cast continue default delete do double dynamic_cast"
" else enum explicit export extern false float for friend goto if inline int long mutable namespace new"
" operator private protected public register reinterpret_cast return short signed"
" sizeof static static_cast struct switch template this throw true try typedef typeid typename union"
" unsigned using virtual void while ";

#define RGB make_rgb

const long RED = RGB(255,0,0), BLUE = RGB(0,0,255), GREEN = RGB(0,80,0),
           LBLUE = RGB(200,200,255), BLACK = 0, YELLOW = RGB(255,255,0),
           PURPLE = RGB(200,0,200);
const int MAX_LINE_LEN = 256, MAX_IDEN_LEN = 40;

char* mSourceExtensions[] = {".C",".CPP",".CXX",".UC",NULL};
char* mHeaderExtensions[] = {".H",".HPP",NULL};

char** source_extensions()
{ return mSourceExtensions; }

char** header_extensions()
{ return mHeaderExtensions; }

bool is_cpp_source_ext(string ext)
{
 return is_one_of(ext,mSourceExtensions);
}

bool is_cpp_header_ext(string ext)
{
 return is_one_of(ext,mHeaderExtensions);
}

bool is_cpp_ext(string ext)
{
 return is_cpp_source_ext(ext) || is_cpp_header_ext(ext);
}

bool is_cpp_keyword(string word)
{
  char* s = strstr(mKeywords,word.c_str());
  if (s) {
    if (s[word.size()] == ' ') return true;
	else return false;

  } else return false;
}


ScintillaWin::ScintillaWin(TWin *parent)
   : TControl(parent,"Scintilla","fred"),
       m_bookmarks(0),m_showing_lines(false), m_folding(false),m_modified(false)
   {}

char* ScintillaWin::get_text()
{
   int sz = length(); 
   if (sz > 0) {
       char* buff = new char[sz+1];
       *buff = '\0';
       send_msg(SCI_GETTEXT,sz+1,(long)buff);
       return buff;
   } else
   return NULL;
}

int ScintillaWin::get_text(long start, long end, char* buff)
{
 TextRange r;
 r.chrg.cpMin = start;
 r.chrg.cpMax = end;
 r.lpstrText = buff;
 return send_msg(SCI_GETTEXTRANGE,0,(long)&r);
}

char* ScintillaWin::get_text_lines(int l1, int l2)
{
  long p1 = get_pos_of_line_start(l1);
  long p2 = get_pos_of_line_end(l2);
  char* buff = new char[p2-p1+1];
  get_text(p1,p2,buff);
  return buff;
}

#define OUT(x) cout << #x << " = " << x << endl

void ScintillaWin::set_lexer(const char* path)
{
    // was it a C/C++ file?
    string ext = get_extension(path);
    if (is_cpp_ext(ext)) {
       send_msg(SCI_SETLEXER,SCLEX_CPP);
       send_msg(SCI_SETKEYWORDS, 0, (long)mKeywords);
       send_msg(SCI_COLOURISE,0,-1);
    }
}

bool ScintillaWin::load_file(const char* path, long pos)
{
    char* buff;
    FILE* in = fopen(path,"rb");
    if (! in) return false;
    fseek(in,0,SEEK_END);
    int sz = ftell(in);
    fseek(in,0,SEEK_SET);
    buff = new char[sz+1];
    fread(buff,sz,1,in);
    fclose(in);
    buff[sz] = '\0';
    if (pos == 0) { // filling Scintilla, setting appropriate lexer
      set_lexer(path);
      set_text(buff); 
    } else {        // otherwise, just inserting stuff into a view
      replace_selection(buff);
    }
    delete buff;
    return true;
  }

bool ScintillaWin::save_file(const char* path, long pos)
{
    FILE* out = fopen(path,"wb");
    if (! out) return false;
    int sz;
    char *p;
    if (pos == 0) { // save the works!
       sz = length();
       if (sz == 0) return false;
       p = get_text();
    } else {
      long p1 = sel_start(), p2 = sel_end();
      sz = p2 - p1;
      p = new char[sz+1];
      get_selection_text(p);
    }
    fwrite(p,sz,1,out);
    fclose(out);
    if (pos == 0) delete p;  // why? seems to blow up
    if (pos == 0) set_lexer(path);
    return true;
}

bool ScintillaWin::modified(bool yesno)
{
    if (yesno != m_modified) {
      m_modified = yesno;
      return true;
    } else return false;
}

void ScintillaWin::style_colour(int style, long colour, bool fore)
{
    send_msg(fore ? SCI_STYLESETFORE : SCI_STYLESETBACK, style, colour);
}

bool ScintillaWin::style_matches(long p, int style_bits)
{
   if (p == CURRENT_POS) p = pos();
   int style = get_style_at(p);
   if ((style_bits & C_PREPROCESSOR) && style == SCE_C_PREPROCESSOR) return true;
   if ((style_bits & C_STRING) && style == SCE_C_STRING) return true;
   if ((style_bits & C_COMMENT) && 
       (style == SCE_C_COMMENT || style == SCE_C_COMMENTLINE ||
        style == SCE_C_COMMENTDOC || style == SCE_C_COMMENTLINEDOC)) return true;
   return false;
}


int ScintillaWin::init()
{
    send_msg(SCI_CLEARDOCUMENTSTYLE,0);

    // we're doing our own popup menu
    send_msg(SCI_USEPOPUP,0);

    // we only want to hear about actual text changes to this Scintilla
	send_msg(SCI_SETMODEVENTMASK,SC_MOD_INSERTTEXT|SC_MOD_DELETETEXT);

    style_colour(SCE_C_COMMENTLINE,RED);
    style_colour(SCE_C_COMMENT,RED);
    style_colour(SCE_C_STRING,GREEN);
    style_colour(SCE_C_WORD,BLUE);
    style_colour(SCE_C_IDENTIFIER,BLACK);
    style_colour(SCE_C_DEFAULT,BLACK);
    style_colour(SCE_C_PREPROCESSOR,GREEN);
    style_colour(SCE_C_COMMENTDOC,PURPLE);
    style_colour(SCE_C_COMMENTLINEDOC,PURPLE);

    style_colour(STYLE_BRACELIGHT,RED,false);

    // bookmark,breakpoint and exec point marker styles
    send_msg(SCI_MARKERDEFINE, BOOKMARK, SC_MARK_ROUNDRECT);
    send_msg(SCI_MARKERSETFORE,BOOKMARK, BLACK);
    send_msg(SCI_MARKERSETBACK,BOOKMARK, LBLUE);

    send_msg(SCI_MARKERDEFINE, BREAKPOINT, SC_MARK_SMALLRECT);
    send_msg(SCI_MARKERSETFORE,BREAKPOINT, BLACK);
    send_msg(SCI_MARKERSETBACK,BREAKPOINT, RED);

    send_msg(SCI_MARKERDEFINE, EXEC_POINT, SC_MARK_ARROW);
    send_msg(SCI_MARKERSETFORE,EXEC_POINT, BLACK);
    send_msg(SCI_MARKERSETBACK,EXEC_POINT, YELLOW);

    // we want folding info...
    set_prop("fold","1");   
    send_msg(SCI_SETFOLDFLAGS,16,0);

    // tabs are evil! But generally I like them to mean 4 spaces.
    send_msg(SCI_SETUSETABS,0);
    send_msg(SCI_SETTABWIDTH,4);
    send_msg(SCI_SETTABINDENTS,1);

    // explicit cr-lf (for now)
//    send_msg(SCI_CONVERTEOLS, 0);
   // send_msg(SCI_SETEOLMODE, 2);
 //   send_msg(SCI_SETVIEWEOL, 1);

	send_msg(SCI_SETMOUSEDWELLTIME,300);

    // *hack*
    send_msg(SCI_ZOOMIN);
    send_msg(SCI_ZOOMIN);
    return 1;
}

int  ScintillaWin::get_fold_level(int l)
{
  return (send_msg(SCI_GETFOLDLEVEL,l-1) & SC_FOLDLEVELNUMBERMASK) - SC_FOLDLEVELBASE;
}

bool ScintillaWin::blank_line(int l)
{
 return (send_msg(SCI_GETFOLDLEVEL,l-1) & SC_FOLDLEVELWHITEFLAG) != 0;
}

long ScintillaWin::matching_brace()
{
 long curr_pos = pos();
 long bpos = brace_match(curr_pos);
 if (bpos != -1) return bpos;
 else { // we may have been just ahead of the brace...
    curr_pos--;
    bpos = brace_match(curr_pos);
    return bpos;    
 } 
}

bool ScintillaWin::find(string text, int flags)
{
 long fpos = pos();
 TextToFind tf;
 tf.lpstrText = (char*)text.c_str();
 tf.chrg.cpMin = fpos+1;
 tf.chrg.cpMax = length();
 fpos = send_msg(SCI_FINDTEXT, flags, (long)&tf);
 if (fpos >= 0) { // we found something...
   set_focus();
   goto_position(fpos);
   set_selection(tf.chrgText.cpMin,tf.chrgText.cpMax);
   send_msg(SCI_FINDTEXT, flags, (long)&tf);
   return true;
 } else return false;
}

int ScintillaWin::goto_line(int l)
{ 
    int ret = send_msg(SCI_GOTOLINE, l-1);
    // this forces the line w/ the caret to be in the centre of the page
    send_msg(SCI_SETYCARETPOLICY,CARET_STRICT | CARET_EVEN,5);
    send_msg(SCI_SCROLLCARET);
    send_msg(SCI_SETYCARETPOLICY,CARET_SLOP,0);
    return ret;
}

static int mmask(int ms)
{
 return 1 << ms;
}

bool ScintillaWin::has_marker(int ms, int line)
{
 int mask = send_msg(SCI_MARKERGET,line-1,0);
 if (mask & mmask(ms)) return true;
 else return false;
}

bool ScintillaWin::delete_marker(int ms, int line)
{
   if (has_marker(ms,line)) {
     send_msg(SCI_MARKERDELETE,line-1,ms);
     return true;
   } else return false;
}

bool ScintillaWin::add_marker(int ms, bool do_toggle)
{
  int line = current_line();
  bool ret = has_marker(ms,line);
  if (ret && do_toggle) {
    delete_marker(ms,line);
    m_bookmarks--;
  }
  else {
    send_msg(SCI_MARKERADD,line-1,ms);
    m_bookmarks++;
  }
  return ret;
}

bool ScintillaWin::next_marker(int ms, bool do_cycle)
{
 int cl = current_line();
 int mask = mmask(ms);
 int ml = send_msg(SCI_MARKERNEXT,cl,mask)+1;
 if (ml > 0) {
   goto_line(ml);
   return true;
 } else
 if (do_cycle && m_bookmarks > 0) {
 // otherwise, try to find the first mark, but only if there _are_ bookmarks
   ml = send_msg(SCI_MARKERNEXT,0,mask)+1;
   if (ml > 0)
     goto_line(ml);
   return true;
 } else return false;
}

bool ScintillaWin::last_marker(int ms)
{
 long l = send_msg(SCI_MARKERPREVIOUS,current_line()-2,mmask(ms))+1;
 if (l > 0) {
   goto_line(l);
   return true;
 } else
 return false;
}

int ScintillaWin::fetch_markers(int ms, int markers[])
{
 int mask = mmask(ms);
 int ml = send_msg(SCI_MARKERNEXT,0,mask)+1;
 int i = 0;
 while (ml > 0){
   markers[i++] = ml;
   ml = send_msg(SCI_MARKERNEXT,ml,mask)+1;
 } 
 return i;
}

static int number_width(int val)
{
  int nch = 1;
  while (val > 0) {
    val /= 10;
    ++nch;
  }
  return nch;
}

void ScintillaWin::set_line_numbers(bool yesno)
{
  if (m_showing_lines == yesno) return;
  if (! yesno) set_margin_width(0,0);
  else {
     // width in chars
     int w = number_width(line_count());
     // multiply by width of '9'
     w *= send_msg(SCI_TEXTWIDTH, STYLE_LINENUMBER,(long)"9");
     set_margin_width(0,w);
  }
  m_showing_lines = yesno;
}

void ScintillaWin::set_folding(bool yesno)
{
 m_folding = yesno;
 set_margin_width(2, yesno ? 16 : 0);
}

#ifndef _MSC_VER
static bool iscsym(char ch)
{
  return isalnum(ch) || ch == '_';
}
#endif

void ScintillaWin::current_word(string& s, long p /*= -999*/)
{
 char line[MAX_LINE_LEN], buff[MAX_IDEN_LEN];
 if (p == -999) p = pos();
 int l = line_from_pos(p);
 // *fix 0.5.1 we need the actual position in the line, _not_ adjusted for tabs!
 //int col = col_from_pos(p); 
 int col = p - get_pos_of_line_start(l);
 int ic = col;
 get_line(l,line);
 // hunt for begining of word
 if (! iscsym(line[ic])) --ic;  // may be to left of pos?
 if (iscsym(line[ic])) {
    int sz = strlen(line);
    while (ic >= 0 && iscsym(line[ic])) --ic;
    int is = ic+1;
   // hunt for end of word
    ic = is;
    char *p = buff;
    while (ic < sz && iscsym(line[ic])) {
        *p++ = line[ic];
        ++ic;
    }
    *p = '\0';
    puts(buff);
    s = buff;
  } else s = "";
}











